﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="RazorTemplatesManager.cs" company="CodePlex">
//   The MIT License
//   Copyright (c) 2013 Julien Blin
//   
//   Permission is hereby granted, free of charge, to any person obtaining a copy
//   of this software and associated documentation files (the "Software"), to deal
//   in the Software without restriction, including without limitation the rights
//   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//   copies of the Software, and to permit persons to whom the Software is
//   furnished to do so, subject to the following conditions:
//   
//   The above copyright notice and this permission notice shall be included in
//   all copies or substantial portions of the Software.
//   
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//   THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace UCodeFirst.RazorViews
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Text.RegularExpressions;
    using System.Web.Hosting;

    using UCodeFirst.ContentTypes;

    using umbraco.cms.businesslogic.template;
    using Umbraco.Core;
    using Umbraco.Core.IO;
    using Umbraco.Core.Logging;

    /// <summary>
    /// Manager responsible for synchronizing razor templates.
    /// </summary>
    public class RazorTemplatesManager : UCodeFirstManagerBase, IContentTypeTemplateProvider
    {
        /// <summary>
        /// Regular expression for capturing layout.
        /// </summary>
        private static readonly Regex LayoutRegex = new Regex("Layout\\s*=\\s*\\\"(?<alias>.+)\\.cshtml\\s*\\\"\\s*;", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Multiline);

        /// <summary>
        /// The list of template ids associated to content types.
        /// </summary>
        private IDictionary<Type, IList<int>> contentTypeTemplateIds;

        /// <summary>
        /// The templates and files.
        /// </summary>
        private IDictionary<string, Template> templatesAndFiles;

        /// <summary>
        /// Returns <see cref="ManagerPriorities.RazorTemplates"/>.
        /// </summary>
        public override int Priority
        {
            get
            {
                return ManagerPriorities.RazorTemplates;
            }
        }

        /// <summary>
        /// All the managers <see cref="UCodeFirstManagerBase.Synchronize"/> methods will run after all the managers <see cref="UCodeFirstManagerBase.BeforeSynchronization"/> method have run,
        /// <b>only</b> if the synchronization happens.<br />
        /// They run in the order specified by the <see cref="UCodeFirstManagerBase.Priority"/>.
        /// </summary>
        /// <param name="httpApplication">
        ///     The <see cref="UmbracoApplicationBase"/>.
        /// </param>
        /// <param name="applicationContext">
        ///     The <see cref="ApplicationContext"/>.
        /// </param>
        public override void Synchronize(UmbracoApplicationBase httpApplication, ApplicationContext applicationContext)
        {
            var mvcViewsDirectory = HostingEnvironment.MapPath(SystemDirectories.MvcViews);
            Debug.Assert(mvcViewsDirectory != null, "mvcViewsDirectory != null");

            var templateFiles = Directory.GetFiles(mvcViewsDirectory, "*.cshtml", SearchOption.TopDirectoryOnly);
            LogHelper.Debug<RazorTemplatesManager>(Resources.FoundNRazorTemplates.InvariantFormat(templateFiles.Count()));

            this.templatesAndFiles = new Dictionary<string, Template>();
            var allTemplates = Template.GetAllAsList();

            foreach (var templateFile in templateFiles)
            {
                try
                {
                    LogHelper.Debug<RazorTemplatesManager>(Resources.Synchronizing.InvariantFormat(templateFile));

                    var alias = Path.GetFileNameWithoutExtension(templateFile);
                    var existingTemplate = allTemplates.FirstOrDefault(x => x.Alias.InvariantEquals(alias))
                                           ?? Template.MakeNew(alias, UCodeFirstConfig.GetUser());

                    this.templatesAndFiles[templateFile] = existingTemplate;
                    LogHelper.Debug<RazorTemplatesManager>(Resources.Synchronized.InvariantFormat(templateFile));
                }
                catch (Exception ex)
                {
                    throw new UCodeFirstException(Resources.ErrorWhileSynchronizing.InvariantFormat(templateFile), ex);
                }
            }

            this.AssignMasterTemplates();
            this.FillContentTypeTemplateIds();

            LogHelper.Info<RazorTemplatesManager>(Resources.SynchronizedNRazorTemplates.InvariantFormat(templateFiles.Count()));
        }

        /// <summary>
        /// All the managers <see cref="UCodeFirstManagerBase.AfterSynchronization"/> methods will run after all the managers <see cref="UCodeFirstManagerBase.Synchronize"/> method have run,
        /// no matter whether synchronization happens or not.<br />
        /// They run in the order specified by the <see cref="UCodeFirstManagerBase.Priority"/>.
        /// </summary>
        /// <param name="httpApplication">
        ///     The <see cref="UmbracoApplicationBase"/>.
        /// </param>
        /// <param name="applicationContext">
        ///     The <see cref="ApplicationContext"/>.
        /// </param>
        /// <param name="synchronizationHappened">
        /// True if the synchronization happened.
        /// </param>
        public override void AfterSynchronization(UmbracoApplicationBase httpApplication, ApplicationContext applicationContext, bool synchronizationHappened)
        {
            if (synchronizationHappened)
            {
                Debug.Assert(this.templatesAndFiles != null, "this.templatesAndFiles != null");
                var synchronizedTemplateAliases = this.templatesAndFiles.Values.Select(x => x.Alias).ToList();
                var allTemplates = Template.GetAllAsList();
                var templatesToRemove = allTemplates.Where(x => !synchronizedTemplateAliases.InvariantContains(x.Alias) && (x.User.Id == UCodeFirstConfig.GetUser().Id))
                                                    .OrderByDescending(x =>
                                                    {
                                                        var currentTemplate = x;
                                                        var deepLevel = 0;
                                                        while (currentTemplate.MasterTemplate != 0)
                                                        {
                                                            ++deepLevel;
                                                            currentTemplate = allTemplates.First(y => y.Id == currentTemplate.MasterTemplate);
                                                        }

                                                        return deepLevel;
                                                    })
                                                    .ToList();

                foreach (var templateToRemove in templatesToRemove)
                {
                    try
                    {
                        LogHelper.Info<RazorTemplatesManager>(Resources.Deleting.InvariantFormat(templateToRemove.Alias));
                        templateToRemove.delete();
                        UCodeFirstData.DeleteValue<RazorTemplatesManager>(Path.GetFileName(templateToRemove.Alias));
                    }
                    catch (Exception ex)
                    {
                        throw new UCodeFirstException(Resources.ErrorWhileDeleting.InvariantFormat(templateToRemove.Alias), ex);
                    }
                }
            }
        }

        /// <summary>
        /// Gets template ids associated with the <paramref name="contentType"/>.
        /// Will only work if synchronization happened.
        /// </summary>
        /// <param name="contentType">
        /// The content type.
        /// </param>
        /// <returns>
        /// The ids.
        /// </returns>
        public IEnumerable<int> GetTemplateIdsForContentType(Type contentType)
        {
            if (this.contentTypeTemplateIds == null)
            {
                return Enumerable.Empty<int>();
            }

            return this.contentTypeTemplateIds.ContainsKey(contentType) ? this.contentTypeTemplateIds[contentType] : Enumerable.Empty<int>();
        }

        /// <summary>
        /// Assign the master templates.
        /// </summary>
        private void AssignMasterTemplates()
        {
            var allTemplates = Template.GetAllAsList();
            foreach (var templateFile in this.templatesAndFiles.Keys)
            {
                try
                {
                    var template = this.templatesAndFiles[templateFile];
                    var layoutMatch = LayoutRegex.Match(File.ReadAllText(templateFile));
                    var masterTemplateAlias = layoutMatch.Success ? layoutMatch.Groups["alias"].Value : string.Empty;
                    var parentMasterTemplateId = 0;

                    if (!string.IsNullOrEmpty(masterTemplateAlias))
                    {
                        var parentMasterTemplate = allTemplates.FirstOrDefault(x => x.Alias.InvariantEquals(masterTemplateAlias));
                        if (parentMasterTemplate == null)
                        {
                            throw new UCodeFirstException(Resources.UnableToFindTemplate.InvariantFormat(masterTemplateAlias));
                        }

                        parentMasterTemplateId = parentMasterTemplate.Id;
                    }

                    if (template.MasterTemplate != parentMasterTemplateId)
                    {
                        template.MasterTemplate = parentMasterTemplateId;
                        template.Save();
                    }

                    LogHelper.Debug<RazorTemplatesManager>(Resources.Synchronized.InvariantFormat(templateFile));
                }
                catch (Exception ex)
                {
                    throw new UCodeFirstException(Resources.ErrorWhileSynchronizing.InvariantFormat(templateFile), ex);
                }
            }
        }

        /// <summary>
        /// Fill <see cref="contentTypeTemplateIds"/>.
        /// </summary>
        private void FillContentTypeTemplateIds()
        {
            var potentialInheritedTypes =
                TypesScanner.GetClassesInBinThatAreAssignableFrom<ContentTypeBase>()
                            .Select(x => typeof(UCodeFirstTemplatePage<>).MakeGenericType(x))
                            .ToList();
            this.contentTypeTemplateIds = new Dictionary<Type, IList<int>>();

            foreach (var templateFile in this.templatesAndFiles.Keys)
            {
                try
                {
                    var template = this.templatesAndFiles[templateFile];
                    var templateTypeBase = this.GetRazorViewBaseTypeWithCache(templateFile);

                    foreach (var potentialInheritedType in potentialInheritedTypes)
                    {
                        if (potentialInheritedType.IsAssignableFrom(templateTypeBase))
                        {
                            var contentTypeType = potentialInheritedType.GetGenericArguments()[0];
                            if (this.contentTypeTemplateIds.ContainsKey(contentTypeType))
                            {
                                this.contentTypeTemplateIds[contentTypeType].Add(template.Id);
                            }
                            else
                            {
                                this.contentTypeTemplateIds[contentTypeType] = new List<int> { template.Id };
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    throw new UCodeFirstException(Resources.ErrorWhileSynchronizing.InvariantFormat(templateFile), ex);
                }
            }
        }
    }
}
